var Shutter = (function () {

    "use strict";

    include("src/modules/iQuery.js");

    //切换效果
    var Effects = List(
        {
            index: 0,
            animation: 0,
            interval: 100
        },{
            index: 1,
            animation: 1,
            interval: 80
        },{
            index: 2,
            animation: 2,
            interval: 200
        }
    );
    Object.defineProperty(Effects, "random", {
        get: function () {
            //不重复随机效果
            var randomEffect = null;
            var IsRepeat = true;
            while (IsRepeat){
                randomEffect = Effects[Math.floor(Math.random() * this.length)];
                IsRepeat = this.prevEffect === randomEffect;
            }
            this.prevEffect = randomEffect;
            return randomEffect;
        }
    });

    //动画方法
    var Animations = List(
        {
            index: 0,
            method: function fadeOut(shutter, current) {
                $(current).addClass("fadeOut animated");
            }
        },{
            index: 1,
            promise: function(shutter, active, next){
                var isEnd = false;
                return new Promise(function (resolve, reject) {
                    var $active = $(active);
                    var $next = $(next);
                    var $activeList = $active.find(".shutter-slide-cell div");
                    var $nextList = $next.find(".shutter-slide-cell div");
                    var delay = 0;
                    var animateClass = "easeOutBounce1";
                    for(var i=0; i<$activeList.length; i++){

                        if (i > 0 && i % shutter.conf.x === 0) delay += 0.3;
                        $activeList.eq(i).css("animation-delay", delay + "s", true);
                        $nextList.eq(i).css("animation-delay", delay + "s", true);
                        delay += shutter.effect.interval / 1000;
                    }
                    $activeList.eq($activeList.length-1).one("webkitAnimationEnd animationend", function () {
                        if (isEnd) return;
                        isEnd = true;
                        $activeList.css("animation-delay", "", true);
                        $nextList.css("animation-delay", "", true);
                        $active.parent().removeClass(animateClass);
                        resolve();
                    });
                    $active.parent().addClass(animateClass);
                });
            }
        },{
            index: 2,
            promise: function (shutter, active, next) {
                var isEnd = false;
                return new Promise(function (resolve, reject) {
                    var $active = $(active);
                    var $next = $(next);
                    var index = 0;
                    var delay = 0;
                    var animateClass = "slideIn1";
                    for(var i=0; i<shutter.conf.x; i++){
                        delay = shutter.effect.interval * i / 1000 + "s";
                        for (var j=0; j<shutter.conf.y; j++){
                            index = i + j * shutter.conf.x + 1;
                            $active.find(`.shutter-slide-cell:nth-of-type(${index}) div`).css("animation-delay", delay, true);
                            $next.find(`.shutter-slide-cell:nth-of-type(${index}) div`).css("animation-delay", delay, true);
                        }
                    }
                    $active.find(".shutter-slide-cell:last-child").one("webkitAnimationEnd animationend", function () {
                        if (isEnd) return;
                        isEnd = true;
                        $active.find(".shutter-slide-cell div").css("animation-delay", "", true);
                        $next.find(".shutter-slide-cell div").css("animation-delay", "", true);
                        $active.parent().removeClass(animateClass);
                        resolve();
                    });
                    $active.parent().addClass(animateClass);
                });
            }
        },{
            index: 3,
            promise: function (shutter, active, next) {
                var isEnd = false;
                return new Promise(function (resolve, reject) {
                    var $active = $(active);
                    var $next = $(next);
                    var index = 0;
                    var delay = 0;
                    var animateClass = "slideIn1";
                    var list = [];
                    var delay = 0;
                    for(let i=0; i<shutter.conf.x * shutter.conf.y; i++){
                        list[i] = i;
                    }
                    list.sort(()=>{return Math.random() - 0.5}); // 打乱元素索引顺序
                    list.forEach(function (value, index, obj) {
                        $active.find(`.shutter-slide-cell:nth-of-type(${index}) div`).css("animation-delay", delay, true);
                        $next.find(`.shutter-slide-cell:nth-of-type(${index}) div`).css("animation-delay", delay, true);
                    });
                    for(var i=0; i<shutter.conf.x; i++){
                        delay = shutter.effect.interval * i / 1000 + "s";
                        for (var j=0; j<shutter.conf.y; j++){
                            index = i + j * shutter.conf.x + 1;
                            $active.find(`.shutter-slide-cell:nth-of-type(${index}) div`).css("animation-delay", delay, true);
                            $next.find(`.shutter-slide-cell:nth-of-type(${index}) div`).css("animation-delay", delay, true);
                        }
                    }
                    $active.find(".shutter-slide-cell:last-child").one("webkitAnimationEnd animationend", function () {
                        if (isEnd) return;
                        isEnd = true;
                        $active.find(".shutter-slide-cell div").css("animation-delay", "", true);
                        $next.find(".shutter-slide-cell div").css("animation-delay", "", true);
                        $active.parent().removeClass(animateClass);
                        resolve();
                    });
                    $active.parent().addClass(animateClass);
                });
            }
        }
    );

    //特殊数组
    function List() {
        var list = Array.prototype.slice.call(arguments, 0);
        list.add = function (index, value) {
            if (!this[index]){
                this[index] = value;
            } else {
                throw new Error("shutter: index already exists in the list.");
            }
        };
        return list;
    }

    //自定义异常类型
    function shutterError(message) {
        this.name = "shutterError";
        this.message = message || "unknown error";
        this.stack = (new Error()).stack;
    }
    shutterError.prototype = Object.create(Error.prototype);
    shutterError.prototype.constructor = shutterError;

    //插件主体
    class Shutter{

        static get effects(){
            return Effects;
        }

        static get animations(){
            return Animations;
        }

        get slideCount(){
            return this.$container.find(".shutter-slide").length;
        }

        get status(){
            return this._status || 0;
        }
        set status(value){
            this._status = value;
        }

        constructor(selector, options){
            this.selector = selector;
            this.defaults = {
                selector: selector,
                x: 5,
                y: 3,
                width: "auto",
                height: "auto",
                align: "middle",
                image: {
                    size: "contain",
                    position: "center center"
                },
                index: 0,
                effect: [0,1,2],
                autoplay: 3000,
                onSlideChangeStart: ()=>{},
                onSlideChangeEnd: ()=>{}
            };
            this.conf = $.extend({}, this.defaults, options);
            this.init();
        }

        init(){
            var that = this;
            that.index = that.conf.index;
            that.effectIndex = 0;
            that.$container = $(that.selector);
            that.containerWidth = that.$container.width();
            that.containerHeight = that.$container.height();
            that.cellWidth = that.containerWidth / that.conf.x; //数值为小数点会有缝隙
            that.cellHeight = that.containerHeight / that.conf.y; //数值为小数点会有缝隙
            that.$container.find(".shutter-slide").each(function (slide, index) {
                var $slide = $(slide);
                var $image = $slide.find("img");
                var src = $image.attr("src");
                var cellCount = that.conf.x * that.conf.y;
                var rowNumber = 0, colNumber = 0;
                var isLoaded = false;
                $image.on("load", function () {
                    if (isLoaded) return;
                    isLoaded = true;
                    var bg = that.getBackground($image);
                    for(let i=0; i<cellCount; i++){
                        var currentRow = Math.floor(i / that.conf.x);
                        if (currentRow > rowNumber){
                            colNumber = 0;
                            rowNumber = currentRow;
                        }
                        var cellTop = rowNumber * that.cellHeight;
                        var cellLeft = colNumber * that.cellWidth;
                        var bgTop = -bg.startTop + cellTop;
                        var bgLeft = -bg.startLeft + cellLeft;
                        $slide.append(`
                            <div class="shutter-slide-cell" style="width: ${that.cellWidth}px; height: ${that.cellHeight}px; top: ${cellTop}px; left: ${cellLeft}px;">
                                <div style="background: url(${src}) ${0-bgLeft}px ${0-bgTop}px no-repeat; background-size: ${bg.size};"></div>
                            </div>
                        `);
                        colNumber++;
                    }
                });
                $image.attr("src", src);
            });
            that.$container.find(".shutter-slide.active").removeClass("active");
            that.$container.find(`.shutter-slide:nth-child(${that.index+1})`).addClass("active");
            $(document).on("click", "[data-toggle-slide='prev']", function () {
                that.slidePrev();
            });
            $(document).on("click", "[data-toggle-slide='next']", function () {
                that.slideNext();
            });
            that.conf.autoplay && that.autoplay();
        }

        getBackground($image){
            var that = this;
            var bg = { size: null, startTop: 0, startLeft: 0 };
            $image.width(that.containerWidth);
            // var imageWidth = $image.width();
            var imageHeight = $image.height();
            if (that.conf.image.size === "contain"){
                if (imageHeight > that.containerHeight){
                    $image.css({ width: "auto", height: that.containerHeight });
                    bg.size = `auto ${that.containerHeight}px`;
                    bg.startLeft = (that.containerWidth - $image.width()) / 2;
                } else {
                    bg.size = `${that.containerWidth}px auto`;
                    bg.startTop = (that.containerHeight - imageHeight) / 2;
                }
            } else if (that.conf.image.size === "cover"){
                if (imageHeight > that.containerHeight){
                    bg.size = `${that.containerWidth}px auto`;
                    bg.startTop = (that.containerHeight - imageHeight) / 2;
                } else {
                    $image.css({ width: "auto", height: that.containerHeight });
                    bg.size = `auto ${that.containerHeight}px`;
                    bg.startLeft = (that.containerWidth - $image.width()) / 2;
                }
            } else {
                bg.size = that.conf.image.size.replace(/\d+%/g, function (matched, index, obj) {
                    var value = parseFloat(/\d+/.exec(matched));
                    if (index === 0){
                        value = value / 100 * that.containerWidth;
                        $image.width(value);
                    } else {
                        value = value / 100 * that.containerHeight;
                        $image.height(value);
                    }
                    return value + "px";
                });
            }
            if (that.conf.image.position){
                var posArr = that.conf.image.position.replace(/top|left|right|bottom|center/gi, function (matched, index, obj) {
                    switch (matched){
                        case "top": return 0;
                        case "left": return 0;
                        case "right": return that.containerWidth - $image.width();
                        case "bottom": return that.containerHeight - $image.height();
                    }
                    if (matched === "center"){
                        var value = index === 0 ? that.containerWidth - $image.width() : that.containerHeight - $image.height();
                        return value / 2;
                    }
                });
                posArr = posArr.replace(/\d+%/g, function (matched, index, obj) {
                    var value = index === 0 ? that.containerWidth : that.containerHeight;
                    return parseFloat(/\d+/.exec(matched)) / 100 * value + "px";
                }).split(" ");
                bg.startLeft = parseFloat(posArr[0]);
                bg.startTop = parseFloat(posArr[1]);
            }
            return bg;
        }

        slideTo(index, callback){
            if (this.status > 0) return;
            this.status = 1;

            // if (index >= this.slideCount) index = 0;
            // if (index < 0) index = this.slideCount - 1;
            this.$container.find(`.shutter-slide:nth-child(${index+1})`).addClass("next");

            var that = this;
            var cellIndex = 0;
            var intervalId = 0;
            var effectIndex = that.conf.effect instanceof Array ? that.conf.effect[that.effectIndex] : that.conf.effect;
            var effect = that.effect = Effects[effectIndex];
            var cellCount = this.conf.x * this.conf.y;
            var $slide = that.$container.find(".shutter-slide.active");
            var $next = that.$container.find(".shutter-slide.next");
            var animation = Animations[effect.animation];
            var promise = null;
            if (animation.promise){
                promise = animation.promise(that, $slide[0], $next[0]);
            } else {
                promise = new Promise(function (resolve, reject) {
                    intervalId = setInterval(function () {
                        var $cell = $slide.find(`.shutter-slide-cell:nth-of-type(${cellIndex+1})`);
                        animation = Animations[effect.animation] || animation;
                        animation.method(that, $cell[0], cellIndex);
                        if (cellIndex === cellCount-1){
                            $cell.one("webkitAnimationEnd animationend", function () {
                                animation.clear && animation.clear(that);
                                $slide.find(".shutter-slide-cell").attr("class", "shutter-slide-cell");
                                resolve();
                            });
                            clearInterval(intervalId);
                        }
                        cellIndex++;
                    }, effect.interval);
                });
            }

            that.conf.onSlideChangeStart.call($slide[0], that, $next[0]);
            promise.then(function () {
                $slide.removeClass("active");
                $next.removeClass("next").addClass("active");
                that.index = index;
                if (that.conf.effect instanceof Array){
                    that.effectIndex >= that.conf.effect.length-1 ? that.effectIndex=0 : that.effectIndex++;
                }
                that.status = 0;
                callback && callback();
                that.conf.onSlideChangeEnd.call($slide[0], that, $next[0]);
                that.conf.autoplay && that.autoplay();
            });
        }

        slidePrev(callback){
            var that = this;
            that.slideTo(that.index-1 < 0 ? that.slideCount-1 : that.index-1, callback);
        }

        slideNext(callback){
            var that = this;
            that.slideTo(that.index+1 >= that.slideCount ? 0 : that.index+1, callback);
        }

        autoplay(){
            var that = this;
            setTimeout(function () {
                that.slideNext(function () {
                    that.autoplay();
                });
            }, that.conf.autoplay);
        }
    }

    return Shutter;

})();